/*
 * Copyright (c) 2008-2011, Petr Panteleyev
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *    3. The name of the author may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.panteleyev.blogclient;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.concurrent.ExecutionException;

import javax.script.ScriptException;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JColorChooser;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.html.HTML;

import org.panteleyev.blogclient.events.TemplatesChangedEvent;
import org.panteleyev.html.Editor;
import org.panteleyev.html.FontFamilyAction;
import org.panteleyev.html.FontSizeAction;
import org.panteleyev.html.HTMLEntity;
import org.panteleyev.swing.SwingFactory;
import org.panteleyev.swing.UndoRedoHandler;
import org.panteleyev.swing.WindowManager;
import org.panteleyev.swing.WindowManagerEnumerator;
import org.panteleyev.swing.events.FontsUpdatedEvent;
import org.panteleyev.utilities.Typograf;
import org.panteleyev.utilities.events.AppEventListener;

import com.google.common.collect.ImmutableList;


/**
 *
 * @param <T>
 * @author Petr Panteleyev
 */
public abstract class BloggerEditorFrame<T extends UserProfile<?>> extends BloggerFrame<T> implements UndoRedoHandler {

    public BloggerEditorFrame(T profile) {
        super(profile);
        createActions();
        textUndoManager = new TextUndoManager(this);

        addAppEventListener(TemplatesChangedEvent.class, new AppEventListener() {
            @Override
            public void eventReceived(EventObject event) {
                setupTemplateMenu();
            }
        });

        addAppEventListener(FontsUpdatedEvent.class, new AppEventListener() {
            @Override
            public void eventReceived(EventObject event) {
                onFontsUpdatedEvent();
            }
        });
    }

    private void createActions() {
        copyAction = new BloggingKit.CopyAction();
        cutAction = new BloggingKit.CutAction();
        pasteAction = new BloggingKit.PasteAction();

        undoAction = new BloggingKit.UndoAction() {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { undo(); }
        };

        redoAction = new BloggingKit.RedoAction() {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { redo(); }
        };

        russianQuotesAction = new AbstractAction(L10N.MAIN_FRAME_RUSSIAN_QUOTES_ACTION.s()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { onQuotes(HTMLEntity.BDQUO, HTMLEntity.RDQUO); }
        };

        frenchQuotesAction = new AbstractAction(L10N.MAIN_FRAME_FRENCH_QUOTES_ACTION.s()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { onQuotes(HTMLEntity.LAQUO, HTMLEntity.RAQUO); }
        };

        englishQuotesAction = new AbstractAction(L10N.MAIN_FRAME_ENGLISH_QUOTES_ACTION.s()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { onQuotes(HTMLEntity.LDQUO, HTMLEntity.RDQUO); }
        };

        englishSingleQuotesAction = new AbstractAction(L10N.MAIN_FRAME_ENGLISH_SINGLE_QUOTES_ACTION.s()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { onQuotes(HTMLEntity.LSQUO, HTMLEntity.RSQUO); }
        };

        copyrightSymbolAction = new AbstractAction("\u00a9 - Copyright") {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { getEditor().insertUnicodeCharacter(HTMLEntity.COPY); }
        };

        tradeSymbolAction = new AbstractAction("\u2122 - Trade Mark") {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { getEditor().insertUnicodeCharacter(HTMLEntity.TRADE); }
        };

        hellipSymbolAction = new AbstractAction(L10N.MAIN_FRAME_HELLIP_SYMBOL_ACTION.s()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { getEditor().insertUnicodeCharacter(HTMLEntity.HELLIP); }
        };

        editInsertReferenceAction = new AbstractAction(L10N.MAIN_FRAME_INSERT_REFERENCE_ACTION.s(), Icon.RICH_REFERENCE.i()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { onInsertReference(); }
        };
        editInsertReferenceAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_M, BloggingKit.getKeyMask()));

        editInsertImageAction = new AbstractAction(L10N.MAIN_FRAME_INSERT_IMAGE_ACTION.s(), Icon.IMAGE_LINK.i()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { onInsertImageReference(); }
        };
        editInsertImageAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.ALT_MASK | BloggingKit.getKeyMask()));

        typografEditAction = new AbstractAction(L10N.EDITOR_EDIT_TYPOGRAF.s(), Icon.EMPTY.i()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { onEditTypograf(); }
        };
        typografEditAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_T, BloggingKit.getKeyMask()));

        /* Format actions */

        strikeAction = new AbstractAction(L10N.MAIN_FRAME_STRIKE_ACTION.s(), Icon.STRIKE.i()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                getEditor().setStrikeThrough();
            }
        };
        strikeAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_R, BloggingKit.getKeyMask()));

        boldAction = new StyledEditorKit.BoldAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Editor editor = BloggerEditorFrame.this.getEditor();
                if (editor instanceof org.panteleyev.html.HTMLEditor) {
                    super.actionPerformed(e);
                } else {
                    editor.setBold();
                }
            }
        };
        boldAction.putValue(Action.NAME, L10N.MAIN_FRAME_BOLD_ACTION.s());
        boldAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_B, BloggingKit.getKeyMask()));
        boldAction.putValue(Action.SMALL_ICON, Icon.BOLD.i());

        italicsAction = new StyledEditorKit.ItalicAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Editor editor = BloggerEditorFrame.this.getEditor();
                if (editor instanceof org.panteleyev.html.HTMLEditor) {
                    super.actionPerformed(e);
                } else {
                    editor.setItalic();
                }
            }
        };
        italicsAction.putValue(Action.NAME, L10N.MAIN_FRAME_ITALIC_ACTION.s());
        italicsAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_I, BloggingKit.getKeyMask()));
        italicsAction.putValue(Action.SMALL_ICON, Icon.ITALIC.i());

        underscoreAction = new StyledEditorKit.UnderlineAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Editor editor = BloggerEditorFrame.this.getEditor();
                if (editor instanceof org.panteleyev.html.HTMLEditor) {
                    super.actionPerformed(e);
                } else {
                    editor.setUnderline();
                }
            }
        };
        underscoreAction.putValue(Action.NAME, L10N.MAIN_FRAME_UNDERSCORE_ACTION.s());
        underscoreAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_U, BloggingKit.getKeyMask()));
        underscoreAction.putValue(Action.SMALL_ICON, Icon.UNDERLINE.i());

        fontColorAction = new AbstractAction(L10N.MAIN_FRAME_FONT_COLOR_ACTION.s(), Icon.FONT_COLOR.i()) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { onFontColor(); }
        };

        fontBigAction = new AbstractAction("BIG", null) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                getEditor().setFormatTag(HTML.Tag.BIG);
            }
        };

        fontSmallAction = new AbstractAction("SMALL", null) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                getEditor().setFormatTag(HTML.Tag.SMALL);
            }
        };

        fontFamilyAction = new AbstractAction(L10N.MAIN_FRAME_FONT_FAMILY_ACTION.s(), null) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                onSelectFont(evt);
            }
        };

        addTemplateAction = new AbstractAction(L10N.MAIN_FRAME_ADD_TEMPLATE_ACTION.s(), null) {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) { onAddTemplate(); }
        };

        // Predefined menu items
        templateMenu = generateTemplateMenu();
    }

    protected JToolBar createEditorToolBar(boolean floatable, boolean rollover) {
        return SwingFactory.createToolBar(floatable, rollover,
            getUndoAction(),
            getRedoAction(),
            null,
            getCutAction(),
            getCopyAction(),
            getPasteAction()
        );
    }

    public JMenu getTemplateMenu() {
        return templateMenu;
    }

    public AbstractAction getCopyAction() {
        return copyAction;
    }

    public AbstractAction getCutAction() {
        return cutAction;
    }

    public AbstractAction getPasteAction() {
        return pasteAction;
    }

    public AbstractAction getRedoAction() {
        return redoAction;
    }

    public AbstractAction getUndoAction() {
        return undoAction;
    }

    public void undo() {
        textUndoManager.undo();
    }

    public void redo() {
        textUndoManager.redo();
    }

    protected JPopupMenu generateEditorPopupMenu() {
        return createMenu(null, null,
            undoAction,
            redoAction,
            null,
            cutAction,
            copyAction,
            pasteAction,
            null,
            generateQuotesSubmenu(),
            generateSymbolsSubmenu(),
            generateTemplateMenu()
        ).getPopupMenu();
    }

    protected JMenu generateTemplateMenu() {
        JMenu tMenu = createMenu(L10N.MAIN_FRAME_MENU_TEMPLATES.s(), Icon.EMPTY.i(),
            addTemplateAction,
            null
        );

        ArrayList<Template> templates = MainClass.getTemplateManager().getTemplates();

        for (int i = 0; i < templates.size(); i++) {
            Template t = templates.get(i);
            final int index = i;

            AbstractAction tAction = new AbstractAction(t.getName()) {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    onApplyTemplate(index);
                }
            };
            KeyStroke hotKey = t.getHotKey();
            if (hotKey != null) {
                tAction.putValue(Action.ACCELERATOR_KEY, hotKey);
            }

            tMenu.add(tAction);
        }

        return tMenu;
    }

    private void setupTemplateMenu() {
        templateMenu.removeAll();

        templateMenu.add(addTemplateAction);
        templateMenu.add(new JSeparator());

        ArrayList<Template> templates = MainClass.getTemplateManager().getTemplates();

        for (int i = 0; i < templates.size(); i++) {
            Template t = templates.get(i);
            final int index = i;

            AbstractAction tAction = new AbstractAction(t.getName()) {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent evt) { onApplyTemplate(index); }
            };
            KeyStroke hotKey = t.getHotKey();
            if (hotKey != null) {
                tAction.putValue(Action.ACCELERATOR_KEY, hotKey);
            }

            templateMenu.add(tAction);
        }
    }

    protected JMenu generateQuotesSubmenu() {
        return createMenu(L10N.MAIN_FRAME_SUBMENU_QUOTES.s(), Icon.EMPTY.i(),
            russianQuotesAction,
            frenchQuotesAction,
            englishQuotesAction,
            englishSingleQuotesAction
        );
    }

    protected JMenu generateSymbolsSubmenu() {
        return createMenu(L10N.MAIN_FRAME_SUBMENU_SYMBOLS.s(), Icon.EMPTY.i(),
            copyrightSymbolAction,
            tradeSymbolAction,
            hellipSymbolAction
        );
    }

    protected JMenu generateFormatMenu() {
        return createMenu(L10N.MAIN_FRAME_MENU_FORMAT.s(), null,
            boldAction,
            italicsAction,
            underscoreAction,
            strikeAction,
            null,
            createMenu(L10N.MAIN_FRAME_SUBMENU_FONT.s(), Icon.EMPTY.i(),
                new FontFamilyAction("Serif", "Serif"),
                new FontFamilyAction("Sans-Serif", "SansSerif"),
                new FontFamilyAction("Monospaced", "Monospaced"),
                null,
                fontFamilyAction
            ),
            createMenu(L10N.MAIN_FRAME_SUBMENU_SIZE.s(), Icon.EMPTY.i(),
                fontBigAction,
                fontSmallAction,
                null,
                new FontSizeAction("xx-small", 8),
                new FontSizeAction("x-small", 10),
                new FontSizeAction("small", 12),
                new FontSizeAction("medium", 14),
                new FontSizeAction("large", 18),
                new FontSizeAction("x-large", 24),
                new FontSizeAction("xx-large", 32)
            ),
            fontColorAction
        );
    }

    private void onQuotes(char open, char close) {
        getEditor().encloseText(open, close);
    }

    protected void onInsertReference() {
        getEditor().onInsertLink(new InsertReferenceDialog(this));
    }

    protected void onInsertReference(String url) {
        getEditor().onInsertLink(new InsertReferenceDialog(this, url));
    }

    protected void onInsertImageReference() {
        getEditor().onInsertImage(new ImageReferenceDialog(this));
    }

    protected void onInsertImageReference(String url) {
        getEditor().onInsertImage(new ImageReferenceDialog(this, url));
    }

    public AbstractAction getEditInsertImageAction() {
        return editInsertImageAction;
    }

    public AbstractAction getEditInsertReferenceAction() {
        return editInsertReferenceAction;
    }

    public AbstractAction getEditTypografAction() {
        return typografEditAction;
    }

    protected void onEditTypograf() {
        startLongOperation();

        new SwingWorker<String,Object>() {
            @Override
            protected String doInBackground() throws Exception {
                String text = getEditor().getSelectedText();
                if (text == null || text.isEmpty()) {
                    return null;
                } else {
                    return Typograf.process(getEditor().getSelectedText());
                }
            }

            @Override
            protected void done() {
                try {
                    String result = get();
                    if (result != null) {
                        getEditor().insertText(result);
                    }
                } catch (InterruptedException | ExecutionException ex) {
                } finally {
                    finishLongOperation();
                }
            }

        }.execute();
    }

    @Override
    public void updateUndoRedo(boolean canUndo, boolean canRedo) {
        undoAction.setEnabled(canUndo);
        redoAction.setEnabled(canRedo);
    }

    private void onFontColor() {
        Color color = JColorChooser.showDialog(this, "Select Font Color", lastColor);
        if (color != null) {
            lastColor = color;
            getEditor().setForegroundColor(color);
        }
    }

    private void onSelectFont(ActionEvent e) {
        FontFamilyDialog dial = new FontFamilyDialog(this, getSelectedText());
        if (dial.showDialog()) {
            new FontFamilyAction("fontFamilyAction", dial.getFontFamily()).actionPerformed(e);
        }
    }

    protected JMenu createEditMenu(Object... args) {
        return createMenu(L10N.MAIN_FRAME_MENU_EDIT.s(), null, args);
    }

    public TextUndoManager getTextUndoManager() {
        return textUndoManager;
    }

    private void onApplyTemplate(int index) {
        Template t = MainClass.getTemplateManager().getTemplate(index);
        if (t != null) {
            String text = getEditor().getSelectedText();
            String newText;
            try {
                newText = t.apply(text);
                getEditor().insertText(newText);
            } catch (ScriptException ex) {
                MainClass.handleException(this, ex);
            }
        }
    }

    private void onAddTemplate() {
        new TemplateEditor(new Template()).setVisible(true);
    }

    private void onFontsUpdatedEvent() {
        final Editor editor = getEditor();
        if (editor != null) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    editor.fontUpdated();
                }
            });
        }
    }

    public static ImmutableList<BloggerEditorFrame<?>> getAllEditorFrames() {
        final ArrayList<BloggerEditorFrame<?>> list = new ArrayList<>();

        WindowManager.enumerate(BloggerEditorFrame.class, new WindowManagerEnumerator<BloggerEditorFrame>() {
            @Override
            public boolean frameFound(BloggerEditorFrame frame) {
                list.add(frame);
                return false;
            }
        });

        return ImmutableList.copyOf(list);
    }


    public abstract void insertText(String text);
    public abstract String getSelectedText();

    public abstract Editor getEditor();

    // Actions
    private AbstractAction undoAction;
    private AbstractAction redoAction;
    private AbstractAction cutAction;
    private AbstractAction copyAction;
    private AbstractAction pasteAction;
    private AbstractAction russianQuotesAction;
    private AbstractAction frenchQuotesAction;
    private AbstractAction englishQuotesAction;
    private AbstractAction englishSingleQuotesAction;
    private AbstractAction copyrightSymbolAction;
    private AbstractAction tradeSymbolAction;
    private AbstractAction hellipSymbolAction;
    private AbstractAction editInsertReferenceAction;
    private AbstractAction editInsertImageAction;
    private AbstractAction typografEditAction;
    private AbstractAction underscoreAction;
    private AbstractAction strikeAction;
    private AbstractAction boldAction;
    private AbstractAction italicsAction;
    private AbstractAction fontColorAction;
    private AbstractAction fontBigAction;
    private AbstractAction fontSmallAction;
    private AbstractAction fontFamilyAction;
    private AbstractAction addTemplateAction;

    private TextUndoManager textUndoManager;

    private JMenu           templateMenu;

    private static Color lastColor = Color.BLACK;
}
